#include "modules/record_mgr.h"
#include "types/recinfo_cache.h"
	
RecordInfo* RecordInfo::ptr = NULL;

RecordId RecordManager::add_record(string &tblname, Record rec, vector<string> index_name)
{
	UINT32 num_attrs = ri.get_record_size(tblname) /sizeof(UINT32);
	vector<string> internal_name(index_name.size());
	
	//printf("\nAffected Indices :");
	for(int i=0; i<num_attrs; i++)
	{
		if(index_name[i] != "")
		{
			bptree index( internal_index_name(index_name[i], tblname) );
			UINT32 *rec_data = (UINT32 *)rec.get_data();		
			Condition cond = {val_check, eq, i, rec_data[i]};
			internal_name[i] = internal_index_name(index_name[i], tblname);
			//printf("%s ",internal_name[i].c_str());
			
			if((!index.isDupAllowed()) && 
				( search_indexed_attr(tblname, index_name[i], cond, index).size() > 0)  )
			{
				throw Exception(recmgr_dup_attr);			
			}
		}
	}
	
	//Insert the record
	rec.id = add_record(tblname, rec, false);
	//printf("\nAdded record : %s %d %d", tblname.c_str(), rec.id.block_num,rec.id.slot_num);
	recinfo_cache::inst()->insert(tblname, rec, internal_name);		
	return rec.id;
}
	
RecordId RecordManager::add_record(string &tablename, Record rec, bool updatecache)
{
	BYTE block[BLOCK_SIZE] = {0};
	//Get the empty slot
	RecordId id = ri.get_empty_record(tablename);
	UINT32 rec_size = ri.get_record_size(tablename);
	//If it is not a new block read the block
	if(id.slot_num != 0)
	{
		bm.read_block(tablename, id.block_num, block);
	}
	memcpy(&block[id.slot_num * rec_size],  rec.get_data(), rec_size);
	bm.write_block(tablename, id.block_num, block);
	rec.id = id;
	if(updatecache)
	{
		recinfo_cache::inst()->insert(tablename, rec);	
	}
	return id;
}

vector<Record> RecordManager::read_records(string &tablename)
{
	vector<Record> result;
	BYTE block[BLOCK_SIZE] = {0};
	UINT32 num_blocks = ri.get_num_blocks(tablename);
	BYTE num_recs = ri.get_max_num_records(tablename);
	UINT32 rec_size = ri.get_record_size(tablename);
	
	for(UINT32 i=0; i<num_blocks; i++)
	{
		if(i == num_blocks - 1)
		{
			num_recs = ri.get_num_records(tablename, i);
		}
		bm.read_block(tablename, i, block);		
		for(BYTE j=0; j<num_recs; j++)
		{
			Record r(rec_size / 4);
			RecordId id = {i,j};
			r.id = id;
			r.set_data(&block[j*rec_size]);
			result.push_back(r);
		}
	}
	return result;
}


void RecordManager::create_index(string &tblname, UINT32 attr_index, string &index_name, bool dupAllowed)
{
	//INDEX CHANGES
	//read records
	vector<Record> records = RecordManager::read_records(tblname);
	vector<index_data> data;
	for(int i=0; i<records.size(); i++)
	{
		if(!recinfo_cache::inst() -> is_cached(records[i].id))
		{
			//printf("\nrecord : %d %d already commited to disk",records[i].id.block_num,records[i].id.slot_num);
			UINT32 *rec_data = (UINT32 *)records[i].get_data();
			index_data entry = {rec_data[attr_index], records[i].id};
			data.push_back(entry);
		}
		else
		{
			//Insert a duplicate record cotaining index name into the cache
			//printf("\nrecord : %d %d is uncommited",records[i].id.block_num,records[i].id.slot_num);			
			UINT32 num_attrs = ri.get_record_size(tblname) / sizeof(UINT32);
			vector<string> index(num_attrs);
			for(int j=0; j<num_attrs;j++)
			{
				if(j != attr_index)
				{
					index[j] = "";
				}
				else
				{
					index[j] = internal_index_name(index_name, tblname);
				}
			}
			recinfo_cache::inst() -> insert(tblname, records[i], index);
		}
	}
	
	string i = internal_index_name(index_name, tblname);	
	
	//Create index
	bptree index(i, dupAllowed, data);
}

void RecordManager::set_index_debug(bool state)
{
	bptree::index_debug = state;
}

Record RecordManager::fetch_record(string &tablename,RecordId id)
{
	BYTE block[BLOCK_SIZE] = {0};
	UINT32 rec_size = ri.get_record_size(tablename);
	Record r(rec_size / sizeof(UINT32)); //passing number of attrs
	
	bm.read_block(tablename, id.block_num, block);
	r.set_data((BYTE *) &block[(id.slot_num) * rec_size]);
	r.id = id;
	
	return r;
}

vector<Record> RecordManager::search_indexed_attr(string tblname, string index_name, Condition cond, bptree &index)
{
	//Search index
	vector<Record> result;
	vector<index_data> records = index.lookup(cond);
	for(int i=0; i<records.size(); i++)
	{
		//printf("\nFound match in index %d %d", records[i].id.block_num, records[i].id.slot_num);
		result.push_back(fetch_record(tblname, records[i].id));
	}
	
	//Search uncommitted records
	vector<Record> u_records = recinfo_cache::inst() -> get_uncommitted_records(tblname);

	for(int i=0; i<u_records.size(); i++)
	{
		if(u_records[i].check(cond))
		{
			//printf("\nFound match in uncommited record %d %d", u_records[i].id.block_num, u_records[i].id.slot_num);		
			result.push_back(u_records[i]);
		}
	}
	
	return result;
}

vector<Record> RecordManager::read_records(string &tablename, Condition cond, string index_name)
{
	vector<Record> result;

	if((index_name != "") and (cond.type == val_check))
	{
		//tablename = internal_index_name(index_name,tablename);
		//INDEX CHANGES	
		bptree index(internal_index_name(index_name,tablename));
		result = search_indexed_attr(tablename, index_name, cond, index);
	}
	else
	{
		//Do seq search
		BYTE block[BLOCK_SIZE] = {0};
		UINT32 num_blocks = ri.get_num_blocks(tablename);
		BYTE num_recs = ri.get_max_num_records(tablename);
		UINT32 rec_size = ri.get_record_size(tablename);
		for(UINT32 i=0; i<num_blocks; i++)
		{
			if(i == num_blocks - 1)
			{
				num_recs = ri.get_num_records(tablename, i);
			}
			bm.read_block(tablename, i, block);		
			for(BYTE j=0; j<num_recs; j++)
			{
				Record r(rec_size / 4);
				RecordId id = {i,j};
				r.id = id;
				r.set_data(&block[j*rec_size]);
				if(r.check(cond))
				{
					result.push_back(r);
				}
			}
		}
	}
	return result;
}

class RecordCompare
{
public:
	bool operator() (const Record &r1, const Record &r2)
	{
		return (r1.id.block_num < r2.id.block_num) || (r1.id.slot_num < r2.id.slot_num);
	}
};

vector<Record> RecordManager::merge_records(vector<Record> vec1, vector<Record> vec2)
{
	vector<Record> result(vec1.size() + vec2.size());
	RecordCompare comp;
	vector<Record>::iterator iter =set_union(vec1.begin(), vec1.end(), vec2.begin(),vec2.end(),result.begin(), comp);
	result.erase(iter, result.end());
	return result;
}

vector<Record> RecordManager::common_records(vector<Record> vec1, vector<Record> vec2)
{
	vector<Record> result(vec1.size());
	RecordCompare comp;
	vector<Record>::iterator iter = set_intersection(vec1.begin(), vec1.end(), vec2.begin(),vec2.end(),result.begin(), comp);
	result.erase(iter, result.end());
	return result;
}
